import * as React from "react"
import intl from "react-intl-universal"
import { SourceState, RSSSource } from "../../scripts/models/source"
import { Stack, Label, Dropdown, IDropdownOption, TextField, PrimaryButton, Icon, DropdownMenuItemType, 
    DefaultButton, DetailsList, IColumn, CommandBar, ICommandBarItemProps, Selection, SelectionMode, MarqueeSelection, IDragDropEvents, Link, IIconProps } from "@fluentui/react"
import { SourceRule, RuleActions } from "../../scripts/models/rule"
import { FilterType } from "../../scripts/models/feed"
import { validateRegex } from "../../scripts/utils"
import { RSSItem } from "../../scripts/models/item"
import Parser from "@yang991178/rss-parser"

const actionKeyMap = {
    "r-true": "article.markRead",
    "r-false": "article.markUnread",
    "s-true": "article.star",
    "s-false": "article.unstar",
    "h-true": "article.hide",
    "h-false": "article.unhide",
    "n-true": "article.notify",
    "n-false": "article.dontNotify",
}

type RulesTabProps = {
    sources: SourceState
    updateSourceRules: (source: RSSSource, rules: SourceRule[]) => void
}

type RulesTabState = {
    sid: string
    selectedRules: number[]
    editIndex: number
    regex: string
    fullSearch: boolean
    caseSensitive: boolean
    match: boolean
    actionKeys: string[]
    mockTitle: string
    mockContent: string
    mockResult: string
}

class RulesTab extends React.Component<RulesTabProps, RulesTabState> {
    rulesSelection: Selection
    rulesDragDropEvents: IDragDropEvents
    rulesDraggedItem: SourceRule
    rulesDraggedIndex = -1

    constructor(props) {
        super(props)
        this.state = {
            sid: null,
            selectedRules: [],
            editIndex: -1,
            regex: "",
            fullSearch: false,
            caseSensitive: false,
            match: true,
            actionKeys: [],
            mockTitle: "",
            mockContent: "",
            mockResult: ""
        }
        this.rulesSelection = new Selection({
            getKey: (_, i) => i,
            onSelectionChanged: () => {
                this.setState({selectedRules: this.rulesSelection.getSelectedIndices()})
            }
        })
        this.rulesDragDropEvents = this.getRulesDragDropEvents()
    }

    getRulesDragDropEvents = (): IDragDropEvents => ({
        canDrop: () => true,
        canDrag: () => true,
        onDrop: (item?: SourceRule) => {
            if (this.rulesDraggedItem) {
                this.reorderRules(item)
            }
        },
        onDragStart: (item?: SourceRule, itemIndex?: number) => {
            this.rulesDraggedItem = item
            this.rulesDraggedIndex = itemIndex!
        },
        onDragEnd: () => {
            this.rulesDraggedItem = undefined
            this.rulesDraggedIndex = -1
        },
    })

    reorderRules = (item: SourceRule) => {
        let rules = this.getSourceRules()
        let draggedItems = this.rulesSelection.isIndexSelected(this.rulesDraggedIndex)
        ? this.rulesSelection.getSelection() as SourceRule[]
        : [this.rulesDraggedItem]
  
        let insertIndex = rules.indexOf(item)
        let items = rules.filter(r => !draggedItems.includes(r))
  
        items.splice(insertIndex, 0, ...draggedItems)
        this.rulesSelection.setAllSelected(false)
        let source = this.props.sources[parseInt(this.state.sid)]
        this.props.updateSourceRules(source, items)
    }

    initRuleEdit = (rule: SourceRule = null) => {
        this.setState({
            regex: rule ? rule.filter.search : "",
            fullSearch: rule ? Boolean(rule.filter.type & FilterType.FullSearch) : false,
            caseSensitive: rule ? !(rule.filter.type & FilterType.CaseInsensitive) : false,
            match: rule ? rule.match : true,
            actionKeys: rule ? RuleActions.toKeys(rule.actions) : []
        })
    }

    getSourceRules = () => this.props.sources[parseInt(this.state.sid)].rules

    ruleColumns = (): IColumn[] => [
        {
            isRowHeader: true,
            key: "regex",
            name: intl.get("rules.regex"),
            minWidth: 100,
            maxWidth: 200,
            onRender: (rule: SourceRule) => rule.filter.search
        },
        {
            key: "actions",
            name: intl.get("rules.action"),
            minWidth: 100,
            onRender: (rule: SourceRule) => RuleActions.toKeys(rule.actions).map(k => intl.get(actionKeyMap[k])).join(", ")
        }
    ]

    handleInputChange = (event) => {
        const name = event.target.name as "regex"
        this.setState({[name]: event.target.value})
    }

    sourceOptions = (): IDropdownOption[] => Object.entries(this.props.sources).map(([sid, s]) => ({
        key: sid,
        text: s.name,
        data: { icon: s.iconurl }
    }))
    onRenderSourceOption = (option: IDropdownOption) => (
        <div>
            {option.data && option.data.icon && (
                <img src={option.data.icon} className="favicon dropdown"/>
            )}
            <span>{option.text}</span>
        </div>
    )
    onRenderSourceTitle = (options: IDropdownOption[]) => {
        return this.onRenderSourceOption(options[0])
    }
    onSourceOptionChange = (_, item: IDropdownOption) => {
        this.initRuleEdit()
        this.rulesSelection.setAllSelected(false)
        this.setState({ 
            sid: item.key as string, selectedRules: [], editIndex: -1,
            mockTitle: "", mockContent: "", mockResult: ""
        })
    }

    searchOptions = (): IDropdownOption[] => [
        { key: 0, text: intl.get("rules.title") },
        { key: 1, text: intl.get("rules.fullSearch") }
    ]
    onSearchOptionChange = (_, item: IDropdownOption) => {
        this.setState({ fullSearch: Boolean(item.key) })
    }

    matchOptions = (): IDropdownOption[] => [
        { key: 1, text: intl.get("rules.match") },
        { key: 0, text: intl.get("rules.notMatch") }
    ]
    onMatchOptionChange = (_, item: IDropdownOption) => {
        this.setState({ match: Boolean(item.key) })
    }

    actionOptions = (): IDropdownOption[] => [
        ...Object.entries(actionKeyMap).map(([k, t], i) => {
            if (k.includes("-false")) {
                return [{ key: k, text: intl.get(t) }, { key: i, text: "-", itemType: DropdownMenuItemType.Divider }]
            } else {
                return [{ key: k, text: intl.get(t) }]
            }
        })
    ].flat(1)

    onActionOptionChange = (_, item: IDropdownOption) => {
        if (item.selected) {
            this.setState(prevState => {
                let [a, f] = (item.key as string).split("-")
                let keys = prevState.actionKeys.filter(k => !k.startsWith(`${a}-`))
                keys.push(item.key as string)
                return { actionKeys: keys }
            })
        } else {
            this.setState(prevState => ({ actionKeys: prevState.actionKeys.filter(k => k !== item.key) }))
        }
    }

    validateRegexField = (value: string) => {
        if (value.length === 0) return intl.get("emptyField")
        else if (validateRegex(value) === null) return intl.get("rules.badRegex")
        else return ""
    }

    saveRule = () => {
        let rule = new SourceRule(this.state.regex, this.state.actionKeys, this.state.fullSearch, this.state.caseSensitive, this.state.match)
        let source = this.props.sources[parseInt(this.state.sid)]
        let rules = source.rules ? [ ...source.rules ] : []
        if (this.state.editIndex === -1) {
            rules.push(rule)
        } else {
            rules.splice(this.state.editIndex, 1, rule)
        }
        this.props.updateSourceRules(source, rules)
        this.setState({ editIndex: -1 })
        this.initRuleEdit()
    }
    newRule = () => {
        this.initRuleEdit()
        this.setState({ editIndex: this.getSourceRules().length })
    }
    editRule = (rule: SourceRule, index: number) => {
        this.initRuleEdit(rule)
        this.setState({ editIndex: index })
    }
    deleteRules = () => {
        let rules = this.getSourceRules()
        for (let i of this.state.selectedRules) rules[i] = null
        let source = this.props.sources[parseInt(this.state.sid)]
        this.props.updateSourceRules(source, rules.filter(r => r !== null))
        this.initRuleEdit()
    }

    commandBarItems = (): ICommandBarItemProps[] => [{ 
        key: "new", text: intl.get("rules.new"), iconProps: { iconName: "Add" },
        onClick: this.newRule
    }]
    commandBarFarItems = (): ICommandBarItemProps[] => {
        let items = []
        if (this.state.selectedRules.length === 1) {
            let index = this.state.selectedRules[0]
            items.push({ 
                key: "edit", text: intl.get("edit"), iconProps: { iconName: "Edit" }, 
                onClick: () => this.editRule(this.getSourceRules()[index], index)
            })
        }
        if (this.state.selectedRules.length > 0) {
            items.push({ 
                key: "del", text: intl.get("delete"), 
                iconProps: { iconName: "Delete", style: { color: "#d13438" } },
                onClick: this.deleteRules
            })
        }
        return items
    }

    testMockItem = () => {
        let parsed = { title: this.state.mockTitle }
        let source = this.props.sources[parseInt(this.state.sid)]
        let item = new RSSItem(parsed as Parser.Item, source)
        item.snippet = this.state.mockContent
        SourceRule.applyAll(this.getSourceRules(), item)
        let result = []
        result.push(intl.get(item.hasRead ? "article.markRead" : "article.markUnread"))
        if (item.starred) result.push(intl.get("article.star"))
        if (item.hidden) result.push(intl.get("article.hide"))
        if (item.notify) result.push(intl.get("article.notify"))
        this.setState({ mockResult: result.join(", ") })
    }

    toggleCaseSensitivity = () => {
        this.setState({ caseSensitive: !this.state.caseSensitive })
    }
    regexCaseIconProps = (): IIconProps => ({
        title: intl.get("context.caseSensitive"),
        children: "Aa",
        style: { 
            fontSize: 12, 
            fontStyle: "normal",
            cursor: "pointer",
            pointerEvents: "unset",
            color: this.state.caseSensitive ? "var(--black)" : "var(--neutralTertiary)",
            textDecoration: this.state.caseSensitive ? "underline" : "",
        },
        onClick: this.toggleCaseSensitivity
    })

    render = () => (
        <div className="tab-body">
            <Stack horizontal tokens={{childrenGap: 16}}>
                <Stack.Item>
                    <Label>{intl.get("rules.source")}</Label>
                </Stack.Item>
                <Stack.Item grow>
                    <Dropdown
                        placeholder={intl.get("rules.selectSource")}
                        options={this.sourceOptions()}
                        onRenderOption={this.onRenderSourceOption}
                        onRenderTitle={this.onRenderSourceTitle}
                        selectedKey={this.state.sid}
                        onChange={this.onSourceOptionChange} />
                </Stack.Item>
            </Stack>

            {this.state.sid 
            ? (this.state.editIndex > -1 || !this.getSourceRules() || this.getSourceRules().length === 0 
                ? <>
                    <Label>
                        {intl.get((this.state.editIndex >= 0 && this.state.editIndex < this.getSourceRules().length) ? "edit" : "rules.new")}
                    </Label>
                    <Stack horizontal>
                        <Stack.Item>
                            <Label>{intl.get("rules.if")}</Label>
                        </Stack.Item>
                        <Stack.Item>
                            <Dropdown
                                options={this.searchOptions()}
                                selectedKey={this.state.fullSearch ? 1 : 0}
                                onChange={this.onSearchOptionChange}
                                style={{width: 140}} />
                        </Stack.Item>
                        <Stack.Item>
                            <Dropdown
                                options={this.matchOptions()}
                                selectedKey={this.state.match ? 1 : 0}
                                onChange={this.onMatchOptionChange}
                                style={{width: 130}} />
                        </Stack.Item>
                        <Stack.Item grow>
                            <TextField
                                name="regex"
                                placeholder={intl.get("rules.regex")}
                                iconProps={this.regexCaseIconProps()}
                                value={this.state.regex}
                                onGetErrorMessage={this.validateRegexField} 
                                validateOnLoad={false} 
                                onChange={this.handleInputChange} />
                        </Stack.Item>
                    </Stack>
                    <Stack horizontal>
                        <Stack.Item>
                            <Label>{intl.get("rules.then")}</Label>
                        </Stack.Item>
                        <Stack.Item grow>
                            <Dropdown multiSelect
                                placeholder={intl.get("rules.selectAction")}
                                options={this.actionOptions()}
                                selectedKeys={this.state.actionKeys}
                                onChange={this.onActionOptionChange}
                                onRenderCaretDown={() => <Icon iconName="CirclePlus" />} />
                        </Stack.Item>
                    </Stack>
                    <Stack horizontal>
                        <Stack.Item>
                            <PrimaryButton
                                disabled={this.state.regex.length == 0 || validateRegex(this.state.regex) === null || this.state.actionKeys.length == 0}
                                text={intl.get("confirm")}
                                onClick={this.saveRule} />
                        </Stack.Item>
                        {this.state.editIndex > -1 && <Stack.Item>
                            <DefaultButton
                                text={intl.get("cancel")}
                                onClick={() => this.setState({ editIndex: -1 })} />
                        </Stack.Item>}
                    </Stack>
                </>
                : <>
                    <CommandBar
                        items={this.commandBarItems()}
                        farItems={this.commandBarFarItems()} />
                    <MarqueeSelection selection={this.rulesSelection} isDraggingConstrainedToRoot>
                        <DetailsList compact
                            columns={this.ruleColumns()}
                            items={this.getSourceRules()}
                            onItemInvoked={this.editRule}
                            dragDropEvents={this.rulesDragDropEvents}
                            setKey="selected"
                            selection={this.rulesSelection}
                            selectionMode={SelectionMode.multiple} />
                    </MarqueeSelection>
                    <span className="settings-hint up">{intl.get("rules.hint")}</span>

                    <Label>{intl.get("rules.test")}</Label>
                    <Stack horizontal>
                        <Stack.Item grow>
                            <TextField
                                name="mockTitle"
                                placeholder={intl.get("rules.title")}
                                value={this.state.mockTitle}
                                onChange={this.handleInputChange} />
                        </Stack.Item>
                        <Stack.Item grow>
                            <TextField
                                name="mockContent"
                                placeholder={intl.get("rules.content")}
                                value={this.state.mockContent}
                                onChange={this.handleInputChange} />
                        </Stack.Item>
                        <Stack.Item>
                            <PrimaryButton
                                text={intl.get("confirm")}
                                onClick={this.testMockItem} />
                        </Stack.Item>
                    </Stack>
                    <span className="settings-hint up">{this.state.mockResult}</span>
                </>)
            : (
                <Stack horizontalAlign="center" style={{marginTop: 64}}>
                    <Stack className="settings-rules-icons" horizontal tokens={{childrenGap: 12}}>
                        <Icon iconName="Filter" />
                        <Icon iconName="FavoriteStar" />
                        <Icon iconName="Ringer" />
                        <Icon iconName="More" />
                    </Stack>
                    <span className="settings-hint">
                        {intl.get("rules.intro")}
                        <Link 
                            onClick={() => window.utils.openExternal("https://github.com/yang991178/fluent-reader/wiki/Support#rules")}
                            style={{marginLeft: 6}}>
                            {intl.get("rules.help")}
                        </Link>
                    </span>
                </Stack>
            )}
        </div>
    )
}

export default RulesTab